Skip to content

Java 常见面试题精选

一、核心要点速览

💡 核心考点

  • Java 基础: 面向对象、基本数据类型、访问修饰符
  • 集合框架: HashMap、ArrayList、ConcurrentHashMap
  • 多线程: 线程池、锁机制、volatile、synchronized
  • JVM: 内存模型、垃圾回收、类加载机制
  • 新特性: Lambda、Stream API、Optional

二、重要资源链接

资源链接说明
Java 官方文档docs.oracle.com/javaseOracle 官方文档
Java 教程docs.oracle.com/javase/tutorial官方入门教程
菜鸟教程runoob.com/java中文入门教程
廖雪峰 Javaliaoxuefeng.com/wiki/1252599548343744系统学习教程
Java Guidejavaguide.cnJava 面试指南

三、Java 基础

题目 1:Java 的四大特性是什么?

标准回答:

Java 的四大特性是:封装、继承、多态、抽象

1. 封装(Encapsulation)

java
public class Person {
    // 私有属性,外部无法直接访问
    private String name;
    private int age;
    
    // 公共方法提供访问接口
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public void setAge(int age) {
        if (age > 0 && age < 150) {
            this.age = age;
        }
    }
}

优点:隐藏实现细节,增强安全性,便于修改和维护。

2. 继承(Inheritance)

java
// 父类
public class Animal {
    public void eat() {
        System.out.println("动物在吃东西");
    }
}

// 子类
public class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("狗在吃骨头");
    }
    
    public void bark() {
        System.out.println("汪汪汪");
    }
}

优点:代码复用,扩展性强,支持多态。

3. 多态(Polymorphism)

java
// 编译时多态(重载)
public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
    
    public double add(double a, double b) {
        return a + b;
    }
}

// 运行时多态(重写)
Animal animal = new Dog();  // 父类引用指向子类对象
animal.eat();  // 输出:"狗在吃骨头"

优点:提高代码灵活性和可扩展性。

4. 抽象(Abstraction)

java
// 抽象类
public abstract class Shape {
    protected String color;
    
    public abstract double getArea();  // 抽象方法
    
    public void draw() {  // 具体方法
        System.out.println("绘制形状");
    }
}

// 接口
public interface Flyable {
    void fly();  // 默认是 public abstract
}

优点:分离接口和实现,降低耦合度。


题目 2:Java 的基本数据类型有哪些?

┌──────────────────────────────────────────────────────────┐
│              Java 基本数据类型                            │
└──────────────────────────────────────────────────────────┘

整数类型:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
byte    : 1 字节  (-128 ~ 127)
short   : 2 字节  (-32768 ~ 32767)
int     : 4 字节  (最常用)
long    : 8 字节  (需要在数值后加 L)

浮点类型:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
float   : 4 字节  (单精度,需要加 F)
double  : 8 字节  (双精度,推荐使用)

字符类型:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
char    : 2 字节  ('A', '中', '\u0041')
          使用 Unicode 编码

布尔类型:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
boolean : true / false
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

包装类:
  byte → Byte
  short → Short
  int → Integer
  long → Long
  float → Float
  double → Double
  char → Character
  boolean → Boolean

标准回答:

Java 有 8 种基本数据类型,分为四类:

整数类型(4 种)

  • byte:1 字节,范围 -128~127
  • short:2 字节
  • int:4 字节(最常用)
  • long:8 字节,声明时要加 L,如 long num = 100L

浮点类型(2 种)

  • float:4 字节,单精度,声明时要加 F,如 float f = 3.14F
  • double:8 字节,双精度(推荐使用)

字符类型(1 种)

  • char:2 字节,使用 Unicode 编码,可以存储中文

布尔类型(1 种)

  • boolean:只有 true 和 false 两个值

注意事项

  • 基本数据类型不是对象,存储在栈中
  • 每个基本类型都有对应的包装类
  • Java 5 引入了自动装箱和拆箱机制

题目 3:访问修饰符有哪些?权限范围是什么?

┌──────────────────────────────────────────────────────────┐
│              访问修饰符权限范围                           │
└──────────────────────────────────────────────────────────┘

权限对比表:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
修饰符        │ 当前类 │ 同包 │ 子类 │ 其他包
─────────────┼───────┼─────┼─────┼───────
private      │   ✓   │  ✗  │  ✗  │   ✗
default      │   ✓   │  ✓  │  ✗  │   ✗
protected    │   ✓   │  ✓  │  ✓  │   ✗
public       │   ✓   │  ✓  │  ✓  │   ✓
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

使用示例:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
public class MyClass {
    // 只能在本类访问
    private int privateVar;
    
    // 同包可访问(不写修饰符)
    int defaultVar;
    
    // 同包或子类可访问
    protected int protectedVar;
    
    // 任何地方都可访问
    public int publicVar;
}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

标准回答:

Java 有四种访问修饰符,权限从小到大:

1. private(私有)

  • 只能在本类中访问
  • 用于隐藏类的内部实现

2. default(默认,不写修饰符)

  • 可以在本类和同包的其他类访问
  • 也称为包级私有

3. protected(受保护)

  • 可以在本类、同包、不同包的子类访问
  • 常用于父类提供给子类的访问接口

4. public(公共)

  • 可以在任何地方访问
  • 用于对外公开的接口

记忆口诀:私默保公,权限递增!


四、集合框架

题目 4:ArrayList 和 LinkedList 的区别?

┌──────────────────────────────────────────────────────────┐
│          ArrayList vs LinkedList                          │
└──────────────────────────────────────────────────────────┘

数据结构:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ArrayList:  动态数组(连续内存)
LinkedList: 双向链表(非连续内存)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

性能对比:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
操作          │ ArrayList │ LinkedList
─────────────┼───────────┼────────────
随机访问 get  │   O(1) ✓  │   O(n)
添加 add      │   O(n)    │   O(1) ✓
删除 remove   │   O(n)    │   O(1) ✓
插入 insert   │   O(n)    │   O(1) ✓
内存占用      │   较少 ✓  │   较多
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

使用场景:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ArrayList:
  ✓ 读多写少
  ✓ 需要随机访问
  ✓ 尾部操作频繁
  
LinkedList:
  ✓ 写多读少
  ✓ 频繁插入删除
  ✓ 中间操作频繁
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

标准回答:

ArrayList 和 LinkedList 都是 List 接口的实现类,但底层数据结构不同:

数据结构

  • ArrayList:基于动态数组,内存连续
  • LinkedList:基于双向链表,内存不连续

性能差异

  • 随机访问:ArrayList 支持通过下标快速访问 O(1),LinkedList 需要遍历 O(n)
  • 插入删除:LinkedList 只需修改指针 O(1),ArrayList 需要移动元素 O(n)

使用场景

java
// ArrayList 适合读多写少
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
String item = list.get(0);  // 快速访问

// LinkedList 适合频繁插入删除
List<String> list = new LinkedList<>();
list.add("A");
list.add(1, "B");  // 中间插入
list.remove(0);    // 删除元素

实际开发:90% 的场景使用 ArrayList,只有在频繁插入删除的中间操作时才考虑 LinkedList。


题目 5:HashMap 的工作原理是什么?

┌──────────────────────────────────────────────────────────┐
│              HashMap 工作原理                             │
└──────────────────────────────────────────────────────────┘

数据结构 (JDK 1.8+):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
数组 + 链表 + 红黑树

┌─────────────────────────────────────┐
│           HashMap                   │
├─────────────────────────────────────┤
│  table[] (Node 数组)                │
│  ├─ [0] → Node → Node → null       │
│  ├─ [1] → Node → TreeNode(红黑树)  │
│  ├─ [2] → null                     │
│  └─ [3] → Node → null              │
└─────────────────────────────────────┘

转换阈值:
  链表长度 ≥ 8 → 转为红黑树
  链表长度 ≤ 6 → 转为链表
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

put 流程:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 计算 key 的 hash 值
   hash = key.hashCode() ^ (hash >>> 16)
   
2. 计算数组索引
   index = (n-1) & hash
   
3. 判断该位置是否为空
   - 空:直接放入
   - 非空:遍历链表/红黑树
     • key 已存在:覆盖 value
     • key 不存在:尾插法添加

4. 检查是否需要扩容
   size > threshold → resize()
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

关键特性:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✓ 允许一个 null key 和多个 null value
✓ 非线程安全
✓ 初始容量 16,负载因子 0.75
✓ JDK 1.8 优化:链表转红黑树
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

标准回答:

HashMap 是基于哈希表的 Map 接口实现,JDK 1.8 之后采用数组 + 链表 + 红黑树的数据结构。

工作原理

  1. 调用 key 的 hashCode() 方法计算哈希值
  2. 通过扰动函数和位运算确定数组索引:index = (n-1) & hash
  3. 如果该位置为空,直接放入;否则遍历链表或红黑树
  4. 如果 key 已存在则覆盖 value,否则添加到末尾
  5. 当元素个数超过容量×负载因子时,触发扩容

JDK 1.8 的优化

  • 当链表长度≥8 时,转换为红黑树(查找效率从 O(n) 提升到 O(log n))
  • 当链表长度≤6 时,转回链表
  • 使用尾插法,避免头插法导致的死循环问题

关键参数

  • 初始容量:16(必须是 2 的幂)
  • 负载因子:0.75(平衡时间和空间)
  • 扩容阈值:容量×负载因子

注意事项

  • HashMap 是非线程安全的
  • 允许 null 键和 null 值
  • 迭代器是 fail-fast 的

题目 6:HashMap 和 ConcurrentHashMap 的区别?

标准回答:

HashMap 和 ConcurrentHashMap 的主要区别在于线程安全性实现方式

1. 线程安全性

  • HashMap:非线程安全,多线程环境下可能导致数据丢失或死循环
  • ConcurrentHashMap:线程安全,支持高并发读写

2. 实现方式

java
// JDK 1.7 ConcurrentHashMap
Segment 分段锁(ReentrantLock)
┌──────────────────────────────┐
│ ConcurrentHashMap            │
│ ├─ Segment[0] (锁)           │
│ │   └─ HashEntry[]           │
│ ├─ Segment[1] (锁)           │
│ └─ ...                       │
└──────────────────────────────┘

// JDK 1.8 ConcurrentHashMap
CAS + synchronized + 节点锁
┌──────────────────────────────┐
│ ConcurrentHashMap            │
│ └─ Node[] table              │
│    ├─ 头节点锁 (synchronized) │
│    └─ CAS 保证可见性          │
└──────────────────────────────┘

3. 性能对比

java
// HashMap(最快,但不安全)
Map<String, Object> map = new HashMap<>();

// ConcurrentHashMap(推荐)
Map<String, Object> map = new ConcurrentHashMap<>();

// Hashtable(已过时,全表锁,性能差)
Map<String, Object> map = new Hashtable<>();

4. 其他区别

  • HashMap 允许 null 键值,ConcurrentHashMap 不允许
  • ConcurrentHashMap 的迭代器是弱一致性的,不会抛出 ConcurrentModificationException

使用建议

  • 单线程环境:HashMap
  • 多线程环境:ConcurrentHashMap(首选)
  • 读多写少:CopyOnWriteArrayList

五、多线程与并发

题目 7:创建线程的方式有哪些?

标准回答:

Java 创建线程主要有以下几种方式:

1. 继承 Thread 类

java
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程执行");
    }
}

MyThread thread = new MyThread();
thread.start();

2. 实现 Runnable 接口

java
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("线程执行");
    }
}

Thread thread = new Thread(new MyRunnable());
thread.start();

3. 实现 Callable 接口(有返回值)

java
class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return 100;
    }
}

FutureTask<Integer> task = new FutureTask<>(new MyCallable());
Thread thread = new Thread(task);
thread.start();
Integer result = task.get();  // 获取返回值

4. 使用线程池(推荐)

java
ExecutorService executor = Executors.newFixedThreadPool(4);
executor.submit(() -> {
    System.out.println("线程池执行");
});
executor.shutdown();

5. 使用 CompletableFuture(异步编程)

java
CompletableFuture.supplyAsync(() -> {
    return "结果";
}).thenAccept(result -> {
    System.out.println(result);
});

推荐使用线程池,可以避免频繁创建销毁线程,提高性能和资源利用率。


题目 8:线程池的核心参数有哪些?

┌──────────────────────────────────────────────────────────┐
│              线程池核心参数                               │
└──────────────────────────────────────────────────────────┘

ThreadPoolExecutor 构造函数:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
public ThreadPoolExecutor(
    int corePoolSize,              // 核心线程数
    int maximumPoolSize,           // 最大线程数
    long keepAliveTime,            // 空闲线程存活时间
    TimeUnit unit,                 // 时间单位
    BlockingQueue<Runnable> workQueue,  // 工作队列
    ThreadFactory threadFactory,   // 线程工厂
    RejectedExecutionHandler handler    // 拒绝策略
)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

参数详解:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. corePoolSize (核心线程数)
   - 线程池中保持活跃的最小线程数
   - 即使空闲也不会被回收(除非 allowCoreThreadTimeOut)

2. maximumPoolSize (最大线程数)
   - 线程池允许创建的最大线程数量
   - 当队列满且线程数 < max 时,创建新线程

3. keepAliveTime (空闲存活时间)
   - 非核心线程的空闲超时时间
   - 超过此时间的空闲线程会被回收

4. workQueue (工作队列)
   - 存放待执行任务的阻塞队列
   - 常用类型:
     • ArrayBlockingQueue:有界队列
     • LinkedBlockingQueue:无界队列
     • SynchronousQueue:不存储元素的队列
     • PriorityBlockingQueue:优先级队列

5. threadFactory (线程工厂)
   - 用于创建新线程
   - 可自定义线程名称、优先级等

6. rejectedExecutionHandler (拒绝策略)
   - 当队列满且达到最大线程数时的处理策略
   - 四种策略:
     • AbortPolicy:抛异常(默认)
     • CallerRunsPolicy:调用者线程执行
     • DiscardPolicy:直接丢弃
     • DiscardOldestPolicy:丢弃最老任务
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

执行流程:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
提交任务

核心线程数满了吗?
  ├─ 否 → 创建核心线程执行
  └─ 是 → 加入等待队列

队列满了吗?
  ├─ 否 → 排队等待
  └─ 是 → 创建非核心线程

最大线程数满了吗?
  ├─ 否 → 创建非核心线程执行
  └─ 是 → 执行拒绝策略
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

标准回答:

ThreadPoolExecutor 有 7 个核心参数,理解它们对于正确使用线程池至关重要。

核心参数

  1. corePoolSize:核心线程数,线程池维持的最小线程数
  2. maximumPoolSize:最大线程数,线程池允许创建的最大线程数量
  3. keepAliveTime:空闲线程存活时间,超过此时间的空闲线程会被回收
  4. workQueue:工作队列,存放待执行任务的阻塞队列
  5. threadFactory:线程工厂,用于创建新线程
  6. rejectedExecutionHandler:拒绝策略,当队列满且达到最大线程数时的处理方式

工作流程

  1. 提交任务后,先判断核心线程是否已满,未满则创建核心线程执行
  2. 核心线程已满,则加入等待队列
  3. 队列已满,判断线程数是否达到最大值,未达到则创建非核心线程
  4. 线程数已达最大值,执行拒绝策略

常用的四种拒绝策略

  • AbortPolicy(默认):抛出 RejectedExecutionException 异常
  • CallerRunsPolicy:由调用者线程执行该任务
  • DiscardPolicy:直接丢弃任务,不抛异常
  • DiscardOldestPolicy:丢弃队列中最老的任务,然后尝试重新提交

实际应用中,建议使用 ThreadPoolExecutor 构造函数手动创建线程池,而不是使用 Executors 工具类,因为后者可能导致资源耗尽问题。


题目 9:synchronized 和 volatile 的区别?

┌──────────────────────────────────────────────────────────┐
│      synchronized vs volatile                             │
└──────────────────────────────────────────────────────────┘

对比表:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
特性          │ synchronized │ volatile
─────────────┼──────────────┼──────────
修饰对象      │ 方法、代码块   │ 变量
原子性        │ ✓ 保证       │ ✗ 不保证
可见性        │ ✓ 保证       │ ✓ 保证
有序性        │ ✓ 保证       │ ✓ 保证
线程阻塞      │ ✓ 可能       │ ✗ 不会
性能影响      │ 较大         │ 较小
实现原理      │ 监视器锁     │ 内存屏障
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

使用示例:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// synchronized - 保证原子性、可见性、有序性
public synchronized void increment() {
    count++;  // 原子操作
}

// volatile - 只保证可见性、有序性
private volatile boolean flag = false;

public void setFlag() {
    flag = true;  // 立即可见
}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

应用场景:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
synchronized:
  ✓ 计数器累加
  ✓ 单例模式(双重检查锁定)
  ✓ 临界区代码同步
  
volatile:
  ✓ 状态标记量
  ✓ 轻量级同步
  ✓ 配合 CAS 使用
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

标准回答:

synchronized 和 volatile 都是 Java 提供的线程同步机制,但它们的作用和使用场景不同。

主要区别

1. 作用范围

  • synchronized:可以修饰方法和代码块
  • volatile:只能修饰变量

2. 保证的特性

  • synchronized:保证原子性、可见性、有序性
  • volatile:只保证可见性和有序性,不保证原子性

3. 实现原理

  • synchronized:基于 JVM 的监视器锁(Monitor),通过 enter/exit monitor 指令实现
  • volatile:基于内存屏障,禁止指令重排序,强制从主内存读取

4. 性能影响

  • synchronized:重量级锁,可能导致线程阻塞,性能开销较大
  • volatile:轻量级同步,不会阻塞线程,性能较好

使用示例

java
// synchronized 适用场景:需要原子操作
public class Counter {
    private int count = 0;
    
    public synchronized void increment() {
        count++;  // 保证原子性
    }
}

// volatile 适用场景:状态标记
public class Task {
    private volatile boolean running = true;
    
    public void stop() {
        running = false;  // 立即对所有线程可见
    }
}

选择建议

  • 需要保证原子性操作:使用 synchronized 或 Lock
  • 只需要可见性:使用 volatile
  • 高性能要求:优先考虑 volatile + CAS

六、JVM 专题

题目 10:JVM 内存区域是如何划分的?

┌──────────────────────────────────────────────────────────┐
│              JVM 内存结构                                 │
└──────────────────────────────────────────────────────────┘

JDK 1.8 内存模型:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
                    JVM 内存

        ┌──────────────┴──────────────┐
        │                             │
    线程共享                      线程私有
        │                             │
   ┌────┴────┐                  ┌────┴────┐
   │         │                  │         │
 堆内存    方法区              虚拟机栈   本地方法栈
 (Heap)   (Metaspace)        (Stack)   (Native Stack)

                                   程序计数器
                                   (PC Register)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

详细说明:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 堆内存 (Heap) - 最大的一块
   - 存放对象实例
   - 所有线程共享
   - GC 的主要区域
   - 可细分为:
     • 新生代 (Young Generation)
       - Eden 区(8)
       - Survivor 区(1+1)
     • 老年代 (Old Generation)

2. 方法区 (Method Area) - JDK 1.8 称元空间
   - 存储类信息、常量、静态变量
   - 线程共享
   - 包括运行时常量池

3. 虚拟机栈 (Java Stack)
   - 每个方法执行时的内存模型
   - 存储局部变量表、操作数栈
   - 生命周期随方法调用
   - 每个线程私有

4. 本地方法栈 (Native Stack)
   - 为 Native 方法服务
   - 类似虚拟机栈

5. 程序计数器 (PC Register)
   - 记录当前执行的字节码行号
   - 线程私有
   - 唯一不会 OOM 的区域
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

常见问题:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
StackOverflowError:
  - 栈深度超过限制
  - 原因:递归过深或方法调用层次过多

OutOfMemoryError:
  - Java heap space: 堆内存不足
  - Metaspace: 元空间不足
  - GC overhead limit exceeded: GC 时间过长
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

标准回答:

JVM 内存区域分为线程共享和线程私有两大部分。

线程共享区域

  1. 堆内存(Heap):最大的一块内存,存放对象实例,是 GC 的主要区域。可分为新生代(Eden + Survivor)和老年代。
  2. 方法区(Method Area):JDK 1.8 称为元空间(Metaspace),存储类信息、常量、静态变量等。

线程私有区域

  1. 虚拟机栈(Stack):每个方法执行时都会创建一个栈帧,存储局部变量、操作数栈等。
  2. 本地方法栈:为 Native 方法服务。
  3. 程序计数器:记录当前执行的字节码行号,是唯一不会发生 OOM 的区域。

常见内存错误

  • StackOverflowError:栈深度超限(递归过深)
  • OutOfMemoryError: Java heap space:堆内存不足
  • OutOfMemoryError: Metaspace:元空间不足

题目 11:常见的垃圾回收算法有哪些?

┌──────────────────────────────────────────────────────────┐
│              垃圾回收算法                                 │
└──────────────────────────────────────────────────────────┘

四种经典算法:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 标记 - 清除算法 (Mark-Sweep)
   ┌─────────────────────────────┐
   │ 步骤:                       │
   │ 1. 标记出所有需要回收的对象 │
   │ 2. 统一清除被标记的对象     │
   └─────────────────────────────┘
   
   缺点:
     ✗ 效率不高
     ✗ 产生内存碎片

2. 标记 - 复制算法 (Mark-Copy)
   ┌─────────────────────────────┐
   │ 步骤:                       │
   │ 1. 将内存分为两块 A 和 B       │
   │ 2. 存活对象从 A 复制到 B       │
   │ 3. 清理 A 区                   │
   └─────────────────────────────┘
   
   优点:
     ✓ 高效,无碎片
   缺点:
     ✗ 内存利用率低(只有 50%)
   
   应用:新生代(Eden + Survivor)

3. 标记 - 整理算法 (Mark-Compact)
   ┌─────────────────────────────┐
   │ 步骤:                       │
   │ 1. 标记存活对象              │
   │ 2. 将存活对象向一端移动     │
   │ 3. 清理边界外的内存         │
   └─────────────────────────────┘
   
   优点:
     ✓ 无碎片
     ✓ 内存利用率高
   
   应用:老年代

4. 分代收集算法 (Generational GC)
   ┌─────────────────────────────┐
   │ 根据对象生命周期划分:      │
   │ • 新生代:Minor GC          │
   │ • 老年代:Major/Full GC     │
   └─────────────────────────────┘
   
   现代 JVM 都采用此算法
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

常见垃圾收集器:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
串行收集器:
  - Serial:单线程,简单高效
  - 适用:客户端模式

并行收集器:
  - Parallel Scavenge:关注吞吐量
  - Parallel Old:老年代版本

并发收集器:
  - CMS:最短停顿时间
    步骤:初始标记 → 并发标记 → 重新标记 → 并发清除
    
  - G1(Garbage First):JDK 9 默认
    特点:可预测的停顿时间
    步骤:初始标记 → 并发标记 → 最终标记 → 筛选回收
    
  - ZGC:JDK 11+,超低停顿(< 10ms)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

标准回答:

常见的垃圾回收算法有四种:

1. 标记 - 清除算法

  • 先标记需要回收的对象,然后统一清除
  • 缺点:效率不高,会产生内存碎片

2. 标记 - 复制算法

  • 将内存分为两块,存活对象从一块复制到另一块
  • 优点:高效、无碎片
  • 缺点:内存利用率只有 50%
  • 应用:新生代(Eden + Survivor 区)

3. 标记 - 整理算法

  • 标记存活对象后,将它们向一端移动
  • 优点:无碎片,内存利用率高
  • 应用:老年代

4. 分代收集算法(现代 JVM 采用)

  • 根据对象生命周期将堆分为新生代和老年代
  • 新生代使用复制算法,老年代使用整理算法

常见的垃圾收集器

  • CMS:并发收集,最短停顿时间
  • G1:JDK 9 默认,可预测停顿时间
  • ZGC:JDK 11+,停顿时间<10ms

七、Java 8 新特性

题目 12:Java 8 有哪些新特性?

标准回答:

Java 8 是 Java 发展史上的里程碑版本,引入了许多重要特性:

1. Lambda 表达式

java
// 传统写法
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello");
    }
}).start();

// Lambda 写法
new Thread(() -> System.out.println("Hello")).start();

2. Stream API

java
List<String> list = Arrays.asList("a", "b", "c");

// 过滤并转换为大写
list.stream()
    .filter(s -> !s.equals("b"))
    .map(String::toUpperCase)
    .collect(Collectors.toList());
// 结果:["A", "C"]

3. 函数式接口

java
@FunctionalInterface
interface Converter<F, T> {
    T convert(F from);
}

// 使用示例
Converter<String, Integer> converter = Integer::valueOf;
Integer result = converter.convert("123");

4. Optional 类

java
// 避免空指针
Optional<String> optional = Optional.ofNullable(getValue());
String result = optional.orElse("default");

5. 日期时间 API

java
LocalDate date = LocalDate.now();
LocalDateTime dateTime = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = dateTime.format(formatter);

6. 默认方法和静态方法

java
interface MyInterface {
    default void defaultMethod() {
        System.out.println("默认方法");
    }
    
    static void staticMethod() {
        System.out.println("静态方法");
    }
}

7. 重复注解

java
@Repeatable(MyAnnotations.class)
@interface MyAnnotation {}

这些特性让 Java 代码更加简洁、可读性更强,支持了函数式编程风格。


八、记忆口诀

Java 面试题歌诀:

四大特性要记牢,
封装继承和多态。
抽象接口分离清,
面向对象是核心!

八种基本类型,
整浮字布分类。
访问修饰四级,
私默保公权限异!

ArrayList 数组,
随机访问速度快。
LinkedList 链表,
插入删除它优秀!

HashMap 很重要,
数组链表红黑树。
线程安全用 Concurrent,
多线程下不出错!

线程池七参数,
核心最大存活时。
队列工厂拒绝策,
执行流程要清晰!

synchronized 锁,
原子可见有序性。
volatile 轻量大,
可见有序不保原!

JVM 内存五区域,
堆栈方法计数本。
垃圾回收算法好,
标记复制整理妙!

Java8 新特性,
Lambda Stream 不能缺。
Optional 防空针,
日期时间更安全!

九、总结一句话

  • 四大特性: 封装 + 继承 + 多态 + 抽象 = 面向对象基石 🏛️
  • 集合框架: ArrayList + HashMap + ConcurrentHashMap = 数据结构核心 📊
  • 多线程: 线程池 + synchronized + volatile = 并发编程必备
  • JVM: 内存模型 + GC 算法 + 类加载 = 底层原理关键 🔧
  • Java 8: Lambda + Stream + Optional = 现代化语法
最近更新